Skip to content

Conversation

@cloutiertyler
Copy link
Contributor

@cloutiertyler cloutiertyler commented Nov 3, 2025

Description of Changes

This PR is a very large change to the workings of the TypeScript SDK and as such requires a higher bar of testing than other PRs. However, it does several important things:

  1. Unifies the API of the server and client so they not only have the same API, but they actually implement it with the same TypeScript types. This fixes several inconsistencies between them and fixes several small bugs as well.
  2. Closes Typescript sdk: SpacetimeDBProvider does not clean itself up and reloads/exiting doesn't disconnect properly #3365
  3. Closes [Web] useSpacetimeDB hook does not cause rerenders #3431
  4. Closes [Web] useTable fails on camelCase #3435
  5. Subsumes the work done in Code generate filter function for non-unique indexes #3447
  6. Derives all type information on the client from a single RemoteModule type which vastly cleans up the correctness of type checking on the client and helped me to find several small bugs

It accomplishes this by changing code generation of TypeScript on the client to code generation approximately what a developer would manually write in their module. The ultimate goal would be to allow the developer to use the types and functions that they define on in their module directly on the client without needing to do any code generation at all, provided they are using TypeScript on the server and client.

#3365 is resolved by .build()ing the DbConnection inside a React useEffect rather than doing it directly in line with the render of the provider. In order to do that we needed to not expose the DbConnection directly to developers by returning a different type from useSpacetimeDB. useSpacetimeDB now returns a ConnectionState object which is stored as React state and updates when any of the fields change. This change also resolves #3431.

#3435 was the issue that initially lead me down the rabbit hole of unifying the server and the client because it was nearly impossible to track down all the various type functions and how they connect to the values that we code generate on the server. After several hours of attempting this, I decided to clean up the types a bit to be more uniform.

Implementing the unification between the client and the server also necessitated fully implemented parts of the API that were fully implemented on the server, but were broken or missing on the client.

API and ABI breaking changes

[Unification] -> Means that this is breaking behavior for the client SDK, but that the new behavior is identical to the server's existing behavior

Breaking changes:


  • Table accessor names and index accessor names are converted to camelCase on the ctx, so ctx.db.foo_bar is now ctx.db.fooBar

  • [Unification] On the client my_table.iter() returns IterableIterator instead of an Array

  • [Unification] module_bindings now export TypeBuilders for all types instead of a type MyType and object MyType, so instead of using MyType as a type directly, you need to infer the type MyType -> Infer<typeof MyType>.

  • [Unification] We no longer generate and export MyTypeVariants for sum types (these are now accessed by Infer<typeof MyType.variants.myVariant>)

  • [Unification] MyType.getTypeScriptAlgebraicType() has been replaced with MyType.algebraicType

  • useSpacetimeDB() no longer takes type parameters

  • useTable() now takes a TableDef parameter and type params are inferred

  • useTable() now just returns an Array directly instead of a object with { rows }

  • [Unification] ctx.reducers.createPlayer(argA, argB) -> ctx.reducers.createPlayer({ argA, argB })

  • [Unification] ctx.reducers.onCreatePlayer(ctx, argA, argB) -> ctx.reducers.onCreatePlayer(ctx, { argA, argB })

  • [Unification] ctx.reducers.removeOnCreatePlayer(ctx, argA, argB) -> ctx.reducers.removeOnCreatePlayer(ctx, { argA, argB })

  • [Unification] myTable.count(): number -> myTable.count(): bigint

Additive changes:

  • Infer<> now also does InferTypeOfRow<> if applicable
  • Added a useReducer() React hook
  • module_bindings now exports a tables object with references to all the TableDefs
  • module_bindings now exports a reducers object with references to all the ReducerDefs
  • Added a new MyType.create('MyVariant', ...) function in addition to the MyType.MyVariant(...) constructors (this is private)

Notable things that did not change:

  • MyType.serialize(writer: BinaryWriter, value: Infer<typeof MyType>) and MyType.deserialize(reader: BinaryReader): Infer<typeof MyType> are still supported exactly as before.
  • The MyType.MyVariant(...) constructor function on sum types is still present, but implemented with the private MyType.create('MyVariant', ...). We could choose to move away from this API later if we didn't like the variants polluting the namespace

Expected complexity level and risk

4 - This is a deep reaching an complex change for the SDK. For the server, it is much less deep reaching since it reuses much of the same machinery, although it does require thorough testing there as some of the code was modified.

This change is fully localized to TypeScript and does not touch the host (or other languages) at all, and therefore only impacts a beta aspect of SpacetimeDB.

Testing

@cloutiertyler cloutiertyler mentioned this pull request Nov 6, 2025
2 tasks
@cloutiertyler cloutiertyler changed the title Got a big boi comin Unifies server module library and client SDK for TypeScript (and fixes several bugs) Nov 11, 2025
@cloutiertyler cloutiertyler marked this pull request as ready for review November 11, 2025 04:20
@cloutiertyler
Copy link
Contributor Author

Notes from PR review meeting:

  • Indexes are still not camelCase
  • Modify useTable to return tuple of [rows, loading]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants